using System;
using System.Threading.Tasks;

namespace Beeflys
{
    abstract class AActorRef : IActorRef
    {
        public AActorRef(AActor actor, ActorId id)
        {
            this.actor = actor;
            this.Id = id;
        }

        private AActor actor;
        internal AActor Actor 
        {
            get { return this.actor; }
        }

        public bool IsActive
        {
            get { return this.actor.IsActive; }
        }

        public ActorId Id { get; }

        public MessengerOperator<TMessenger> Messenger<TMessenger>(IActorRef source, TimeSpan timeout) 
            where TMessenger : IActorMessengerDefine
        {
            return new MessengerOperator<TMessenger>(this, source, timeout);
        }

        public void Send(object o, IActorRef source)
        {
            ISponsor sponsor;
            if(source == null)
                sponsor = new UsualSendSponsor();
            else
                sponsor = new ActorSendSponsor(source);

            this.WriteToActor(new ActorContext(o, sponsor));
        }
        
        public void Call<TResponse>(object o, TimeSpan timeout, IActorRef source, ResponseDelegate<TResponse> completed)
        {
            if (completed == null)
            {
                throw new ArgumentNullException(nameof(completed));
            }

            ISponsor sponsor;
            if (source == null)
                sponsor = new UsualCallbackSponsor<TResponse>(completed, timeout);
            else
                sponsor = new ActorCallbackSponsor<TResponse>(source, completed, timeout);

            this.WriteToActor(new ActorContext(o, sponsor));
        }

        public Task<ActorResult<TResponse>> CallAsync<TResponse>(object o, TimeSpan timeout, IActorRef source)
        {
            var tcs = new TaskCompletionSource<ActorResult<TResponse>>();
            this.Call<TResponse>(o, timeout, source, (result) => 
            {
                tcs.SetResult(result);
            });

            return tcs.Task;
        }
        
        private void WriteToActor(ActorContext actorContext)
        {
            this.actor.Deliver(actorContext).NoAwait();
        }
    }
}
